local setmetatable = setmetatable
local error = error
local print = print
local pairs = pairs
local type = type
local tostring = tostring
local assert = assert

local snmp = require("snmp")
local mib = snmp.mib

module('fit.snmp.SnmpSession')
_VERSION = '0.2'

do -- Make everything static

local session = {} -- self
session.config = {} -- session connection configuration

-- Data Type Maps
types = {} -- Maps type number to human readable string (see luasnmp doc)
types[1] = "TYPE_OBJID"
types[2] = "TYPE_OCTETSTR"
types[3] = "TYPE_INTEGER"
types[4] = "TYPE_NETADDR"
types[5] = "TYPE_IPADDR"
types[6] = "TYPE_COUNTER"
types[7] = "TYPE_GAUGE"
types[8] = "TYPE_TIMETICKS"
types[9] = "TYPE_OPAQUE"
types[10] = "TYPE_NULL"
types[11] = "TYPE_COUNTER64"
types[12] = "TYPE_BITSTRING"
types[13] = "TYPE_NSAPADDRESS"
types[14] = "TYPE_UINTEGER"
types[15] = "TYPE_UNSIGNED32"
types[16] = "TYPE_INTEGER32"
types[120] = "TYPE_FLOAT"
types[121] = "TYPE_DOUBLE"
types[122] = "TYPE_INTEGER64"
types[123] = "TYPE_UNSIGNED64"

native_types = {} -- maps type# to lua native type (see luasnmp doc)
native_types[1] = "string"
native_types[2] = "string"
native_types[3] = "number"
native_types[4] = "string"
native_types[5] = "string"
native_types[6] = "number"
native_types[7] = "number"
native_types[8] = "table"
native_types[9] = "string"
native_types[10] = "nil"
native_types[11] = "number"
native_types[12] = "string"
native_types[13] = "string"
native_types[14] = "number"
native_types[15] = "number"
native_types[16] = "number"
native_types[120] = "number"
native_types[121] = "number"
native_types[122] = "number"
native_types[123] = "number"

-- DEBUG FUNCTIONS

local function printTable(t, indentStr)
    for k in pairs(t) do

        if type(t[k]) == "table" then
            print(indentStr .. k .. ":")
            local newIndentStr = indentStr .. " "
            printTable(t[k], newIndentStr)

        elseif type(t[k]) == "function" then
            print(indentStr .. k.. ": function");
        elseif type(t[k]) == "boolean" then
            print(indentStr .. k .. ": " .. tostring(t[k]))
        elseif type(t[k]) == "userdata" then
            print(indentStr .. k .. ": userdata")
        else
            print(indentStr .. k .. ": " .. t[k])
        end
    end
end

-- SNMP Functions

local function snmp_open(self)
    self.session, self.sessionError = snmp.open({
                              password=self.config.password,
                              peer=self.config.peer,
                              user=self.config.user,
                              securityLevel=self.config.securityLevel,
                              context=self.config.context,
                              authType=self.config.authType,
                              version=snmp.SNMPv3 -- default
                            })
    if not self.session then
        error("Could not open SNMP session.")
    end
end

local function snmp_close(self)
    snmp.close(self.session)
end

local function snmp_get(self, vars)
    local err, index

    if (vars and vars ~= "") then
        self.vlist, err, index = snmp.get(self.session, vars)
    else
        self.vlist, err, index = snmp.get(self.session, self.vlist)
    end

    if not self.vlist or err then
        error("SNMP get failed: " .. err)
    elseif self.vlist.type == snmp.NOSUCHOBJECT then
        error("NO SUCH OBJECT")
    elseif self.vlist.type == snmp.NOSUCHINSTANCE then
        error("NO SUCH INSTANCE")
    elseif self.vlist.type == snmp.ENDOFMIBVIEW then
        error("END OF MIB VIEW")
    end

    -- Wrap single returns so that they have the same form
    -- as gets that return multiple results, for consistency.
    if self.vlist[1] == nil then
        vb_list = {}
        vb_list[1] = self.vlist
        self.vlist = vb_list
    end
end

local function snmp_getnext(self, var)
    local err, index

    if (var and var ~= "") then
        self.vlist, err, index = snmp.getnext(self.session, var)
    else
        self.vlist, err, index = snmp.getnext(self.session, self.vlist)
    end

    if not self.vlist or err then
        error("SNMP get failed!")
    elseif self.vlist.type == snmp.NOSUCHOBJECT then
        error("NO SUCH OBJECT")
    elseif self.vlist.type == snmp.NOSUCHINSTANCE then
        error("NO SUCH INSTANCE")
    elseif self.vlist.type == snmp.ENDOFMIBVIEW then
        error("END OF MIB VIEW")
    end

    -- Wrap single returns so that they have the same form
    -- as gets that return multiple results, for consistency.
    if self.vlist[1] == nil then
        vb_list = {}
        vb_list[1] = self.vlist
        self.vlist = vb_list
    end

end

local function snmp_set(self)
    self.vlist, err, index = snmp.set(self.session, self.vlist)

    assert(vlist, err)
end

local function snmp_walk(self, var)
    if (var and var ~= "") then
        self.vlist, err, index = snmp.walk(self.session, var)
    else
        self.vlist, err, index = snmp.walk(self.session, self.vlist)
    end

    if not self.vlist or err then
        error("SNMP get failed!")
    elseif self.vlist.type == snmp.NOSUCHOBJECT then
        error("NO SUCH OBJECT")
    elseif self.vlist.type == snmp.NOSUCHINSTANCE then
        error("NO SUCH INSTANCE")
    elseif self.vlist.type == snmp.ENDOFMIBVIEW then
        error("END OF MIB VIEW")
    end
end

local function snmp_getbulk(self, var)
    error("No implemented yet")
end

local function vb_list(self)
    return self.vlist
end

-- Converts SNMP Type number to the matching name
-- See the LuaSNMP docs for details
local function numtostring(type_num)
    return types[type_num]
end


-- Constructor
function __call()
    session.snmp_open = snmp_open
    session.snmp_close = snmp_close
    session.snmp_get = snmp_get
    session.snmp_getnext = snmp_getnext
    session.snmp_set = snmp_set
    session.snmp_walk = snmp_walk
    session.vb_list = vb_list
    session.numtostring = numtostring

    return session
end

end -- end do

setmetatable(_M,_M)
